package org.joa.manager.persistence;

import java.io.IOException;
import java.lang.reflect.Field;
import java.util.UUID;

import org.joa.actor.Actor;
import org.joa.actor.ActorName;
import org.joa.actor.ActorProxyInterface;
import org.joa.manager.ActorManager;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;

public class JSONFilePersistenceManager extends FilePersistenceManager {

    protected static class ProxyTypeAdapterFactory implements TypeAdapterFactory {
        @Override
        public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
            if (ActorProxyInterface.class.isAssignableFrom(typeToken.getRawType())) {
                return new TypeAdapter<T>() {
                    public void write(JsonWriter out, T value) throws IOException {
                        ActorProxyInterface proxyInterface = (ActorProxyInterface) value;
                        if (value == null) {
                            out.beginObject();
                            out.name("remote").value("");
                            out.endObject();
                        } else {
                            out.beginObject();
                            out.name("remote")
                                .value(proxyInterface.getRemoteActorName().toString());
                            out.name("interfaceClass").value(
                                proxyInterface.getProxyInterfaceClass().getName());
                            out.endObject();
                        }
                    }

                    @SuppressWarnings("unchecked")
                    public T read(JsonReader reader) throws IOException {
                        if (reader.peek() == JsonToken.NULL) {
                            reader.nextNull();
                            return null;
                        } else {
                            ActorName remote = null;
                            ActorName owner = null;
                            Class<?> interfaceClass = null;

                            reader.beginObject();
                            while (reader.hasNext()) {
                                String name = reader.nextName();
                                if (name.equalsIgnoreCase("remote")) {
                                    String remoteString = reader.nextString();
                                    if (remoteString != null && remoteString.length() > 0) {
                                        remote = new ActorName(UUID.fromString(remoteString));
                                    }
                                } else if (name.equalsIgnoreCase("interfaceClass")) {
                                    String className = reader.nextString();
                                    try {
                                        interfaceClass = Class.forName(className);
                                    } catch (ClassNotFoundException e) {
                                        throw new RuntimeException(e);
                                    }
                                }
                            }
                            reader.endObject();

                            if (remote == null || interfaceClass == null) {
                                return null;
                            }

                            return (T) ActorManager.instance.createActorProxy(
                                remote,
                                owner,
                                interfaceClass);
                        }
                    }
                };
            }
            return null;
        }
    }

    public JSONFilePersistenceManager(String basePath) {
        super(basePath);
    }

    @Override
    public byte[] serialize(Actor actor) throws IOException {
        GsonBuilder gsonBuilder = new GsonBuilder();
        gsonBuilder.registerTypeAdapterFactory(new ProxyTypeAdapterFactory());
        return gsonBuilder.create().toJson(actor).getBytes();
    }

    @Override
    public Actor deserialize(byte[] data, Class<? extends Actor> actorClass) throws IOException,
        ClassNotFoundException, IllegalArgumentException, IllegalAccessException {
        GsonBuilder gsonBuilder = new GsonBuilder();
        gsonBuilder.registerTypeAdapterFactory(new ProxyTypeAdapterFactory());
        Actor actor = (Actor) gsonBuilder.create().fromJson(new String(data), actorClass);

        // Set owner for proxies
        for (Field field : actor.getClass().getDeclaredFields()) {
            if (ActorProxyInterface.class.isAssignableFrom(field.getType())) {
                field.setAccessible(true);
                ActorProxyInterface proxyInterface = (ActorProxyInterface) field.get(actor);
                if (proxyInterface != null) {
                    proxyInterface.setOwnerActorName(actor.self());
                }
            }
        }

        return actor;
    }
}
